/**
 * My simple header template from child directory
 * TreasureHunter.java - ${projectName} - ${organizationName} - ${inceptionYear}
 */
package FourthTry;

/*
 * #%L
 * Ability-tester
 * %%
 * Copyright (C) 2014 AmazingSERKU
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */


import FourthTry.actions.IAction;
import FourthTry.actions.Move;
import FourthTry.properties.HunterProperty;
import FourthTry.properties.IProperty;
import FourthTry.results.*;

import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: Gauss
 * Date: 08.01.14
 * Time: 0:35
 * To change this template use File | Settings | File Templates.
 */
public class TreasureHunter implements ITreasureHunter, IMovable, IHuman {

    private Map<Class<? extends IProperty>, IReaction> standardReactions;
    private Map<Class<? extends IProperty>, IProperty> standardAnalysis;
    private Set<Class<? extends IResult>> criticalResults;
    //
    private List<IAction> actionList;
    private Stack<ICoordinator> coordinators;
    protected ICoordinator coordinator;
    protected IReaction reaction;
    //
    protected Coordinates coordinates;
    protected CoordinatorCell goal;

    public TreasureHunter() {
        initializeComponents();
        initializeCriticalResult();
    }

    private void initializeComponents() {
        standardAnalysis = new HashMap<Class<? extends IProperty>, IProperty>();
        standardReactions = new HashMap<Class<? extends IProperty>, IReaction>();
        actionList = new ArrayList<IAction>();
        coordinators = new Stack<ICoordinator>();
    }

    private void initializeCriticalResult() {
        criticalResults = new HashSet<Class<? extends IResult>>();
        criticalResults.add(TreasureFound.class);
        criticalResults.add(DeathFound.class);
    }

    @Override
    public IResult searchForTreasure(IArea area) {
        return searchForTreasure(area, new Coordinates(0, 0));
    }

    @Override
    public IResult searchForTreasure(IArea area, Coordinates startCoordinates) {
        //
        coordinates = startCoordinates;
        coordinators.push(new SnakeCoordinator(area, startCoordinates));
        //
        goal = new CoordinatorCell(area.getCell(startCoordinates), false);
        reaction = interactWithCell(goal);
        if (isEnd(reaction)) {
            return reaction;
        }
        coordinator = coordinators.pop();
        coordinatorMove();
        return reaction;
    }

    private boolean isEnd(IResult result) {
        return (criticalResults.contains(result.getClass()));
    }

    private void coordinatorMove() {
        reaction = new NoReaction();
        StepResult stepResult = null;
        while (!isEnd(reaction) && stepResult!=StepResult.END_OF_ALGORITHM) {
            stepResult = takeAStepOrContinueActions(stepResult);
        }
    }

    private StepResult takeAStepOrContinueActions(StepResult stepResult) {
        if (stepResult==StepResult.DEFERRED_STEP) {
            return continueActions();
        }
        return takeAStep();
    }

    private StepResult continueActions() {
        if (doneActions()) {
            return StepResult.STEP_MADE;
        }
        return StepResult.DEFERRED_STEP;
    }

    protected StepResult takeAStep() {
        StepResult result = analyze();
        if (result!=null) {
            return result;
        }
        interact();
        return StepResult.STEP_MADE;
    }

    protected StepResult analyze() {
        boolean canMove = false;
        while(!canMove) {
            goal = coordinator.getNextCell(coordinates);
            canMove = analyzeCell(goal);
            StepResult result = cellDecision(canMove);
            if (result!=null) {
                return result;
            }
        }
        return null;
    }

    protected void interact() {
        reaction = interactWithCell(goal);
        react(reaction);
    }

    protected StepResult cellDecision(boolean canMove) {
        if (goal.isInformational()) {
            if (coordinators.empty()) {
                return StepResult.END_OF_ALGORITHM;
            }
            coordinator = coordinators.pop();
        } else {
            if (!isAllActionsComplete(canMove)) {
                return StepResult.DEFERRED_STEP;
            }
        }
        return null;
    }

    protected boolean isAllActionsComplete(boolean canMove) {
        if (!canMove) {
            MoveAround.affect(this);
        } else {
            if (!doneActions()) {
                return false;
            }
        }
        return true;
    }

    protected boolean doneActions() {
        Iterator<IAction> iterator = actionList.iterator();
        while (iterator.hasNext()) {
            IAction action = iterator.next();
            action.perform(this);
            boolean time = action.takeTime();
            iterator.remove();
            if (time && !actionList.isEmpty()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean analyzeCell(Cell cell) {
        actionList.clear();
        boolean analysisResult = true;
        if (cell.getProperties() != null && cell.getProperties().size()!=0) {
            Iterator<IProperty> iterator = cell.getProperties().iterator();
            while(iterator.hasNext() && analysisResult) {
                analysisResult = chooseAnalysis(iterator.next());
            }
            Collections.sort(actionList, new ActionComparator());
        }
        actionList.add(new Move());
        return analysisResult;
    }

    private boolean chooseAnalysis(IProperty property) {
        IProperty temp = standardAnalysis.get(property.getClass());
        if (temp == null) {
            temp = property;
        }
        addActionToList(temp, property);
        return temp.getAnalysis();
    }

    private void addActionToList(IProperty actionProperty, IProperty areaProperty) {
        if (actionProperty.haveAction()) {
            actionList.add(actionProperty.getAction(areaProperty));
        }
    }

    protected IReaction interactWithCell(Cell cell) {
        if (cell.getProperties() == null || cell.getProperties().size()==0) {
            return new NoReaction();
        }
        IReaction iReaction = new NoReaction();
        for (IProperty property: cell.getProperties()) {
            iReaction = standardReactions.get(property.getClass());
            if (iReaction == null) {
                iReaction = property.getReaction();
                standardReactions.put(property.getClass(), iReaction);
            }
            if (iReaction.isCritical()) {
                break;
            }
        }
        return iReaction;
    }

    protected void react(IReaction reaction) {
        reaction.affect(this);
    }

    public void addCoordinator(ICoordinator coordinator) {
        if (coordinators==null) {
            coordinators = new Stack<ICoordinator>();
        }
        coordinators.push(this.coordinator);
        if (this.coordinator != null) {
            coordinator.copyState(this.coordinator);
        }
        this.coordinator = coordinator;
    }

    @Override
    public Cell getApprovalGoal() {
        boolean result = false;
        Cell cell = new Cell(this.coordinates);
        while(!result) {
            cell = coordinator.getNextCell(cell.getCoordinates());
            result = analyzeCell(cell);
        }
        return cell;
    }

    @Override
    public void setNewGoal(CoordinatorCell goal) {
        this.goal = goal;
    }

    public void addCriticalResult(Class<IResult> clazz) {
        if (criticalResults == null) {
            criticalResults = new HashSet<Class<? extends IResult>>();
        }
        criticalResults.add(clazz);
    }
    //endregion

    @Override
    public void changeCoordinates() {
        this.coordinates = goal.getCoordinates();
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

    @Override
    public void addProperty(HunterProperty property) {
        standardAnalysis.put(property.getProperty(), property);
    }

    @Override
    public void addProperties(HunterProperty[] properties) {
        for (HunterProperty property: properties) {
            standardAnalysis.put(property.getProperty(), property);
        }
    }

    @Override
    public void addProperties(ArrayList<HunterProperty> properties) {
        for (HunterProperty property: properties) {
            standardAnalysis.put(property.getProperty(), property);
        }
    }

    protected enum StepResult {
        STEP_MADE, END_OF_ALGORITHM, DEFERRED_STEP
    }

    protected class ActionComparator implements Comparator<IAction> {

        @Override
        public int compare(IAction o1, IAction o2) {
            if ((o1.isWaiting() && o2.isWaiting()) || (!o1.isWaiting() && !o2.isWaiting())) return 0;
            if (o1.isWaiting() && !o2.isWaiting()) return 1;
            return -1;
        }
    }

}
